# -*- coding: UTF-8 -*-
# File date: Hi_2022/12/4 18:43
# File_name: format_conversion.py
import os
import sys
import time
from io import BytesIO
import requests
from tkinter import Tk, BooleanVar, END, messagebox, PhotoImage
from tkinter.ttk import Label, Button, Entry, Checkbutton
from tkinter.messagebox import showerror
from tkinter.scrolledtext import ScrolledText
from tkinter.filedialog import askdirectory, askopenfilename
from os.path import dirname, isfile, join, abspath
from PIL import Image, ImageTk, UnidentifiedImageError


def get_resource_path(relative_path):
    """
    根据传入相对路径获取资源路径
    在需要用到非py文件时用get_resource_path(path)替换原本直接使用相对路径方式即可
    此方法适用于，在进行pyinstaller打包加了-F参数情况，否则建议不使用此方法获得更小的包
    """
    if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
        return join(sys._MEIPASS, relative_path)
    return join(abspath(""), relative_path)


class GO_ICO:
    def __init__(self, windows):
        # 窗口初始化
        self.win = windows

        # 尺寸
        self.sizes = list()

        # 8个转换尺寸绑定变量
        self.s24 = BooleanVar(value=True)
        self.s32 = BooleanVar(value=True)
        self.s48 = BooleanVar(value=True)
        self.s64 = BooleanVar(value=True)
        self.s96 = BooleanVar(value=True)
        self.s128 = BooleanVar(value=True)
        self.s192 = BooleanVar(value=False)
        self.s256 = BooleanVar(value=False)
        self.sdiy = BooleanVar(value=False)  # 其他尺寸变量

        self.other_text = ScrolledText(self.win)  # 其他尺寸文本框

        self.selection_path = Entry(self.win)  # 选择文件路径展示Entry
        self.output_path = Entry(self.win)  # 输出文件路径展示Entry

        # 界面布局
        self.gui_init()

    def gui_init(self):
        """组件和布局"""
        # 组件-----------------------------------------------------------------------------------------------------
        satrt_but = Button(self.win, text="开始转换", cursor="hand2")  # 开始转换按钮
        selection = Button(self.win, text="选择文件", cursor="hand2")  # 选择文件按钮
        output = Button(self.win, text="保存至", cursor="hand2")  # 输出路径选择按钮

        # 选择转换尺寸
        but24 = Checkbutton(self.win, text="24*24", variable=self.s24, onvalue=True, offvalue=False, cursor="hand2")
        but32 = Checkbutton(self.win, text="32*32", variable=self.s32, onvalue=True, offvalue=False, cursor="hand2")
        but48 = Checkbutton(self.win, text="48*48", variable=self.s48, onvalue=True, offvalue=False, cursor="hand2")
        but64 = Checkbutton(self.win, text="64*64", variable=self.s64, onvalue=True, offvalue=False, cursor="hand2")
        but96 = Checkbutton(self.win, text="96*96", variable=self.s96, onvalue=True, offvalue=False, cursor="hand2")
        but128 = Checkbutton(self.win, text="128*128", variable=self.s128, onvalue=True, offvalue=False, cursor="hand2")
        but192 = Checkbutton(self.win, text="192*192", variable=self.s192, onvalue=True, offvalue=False, cursor="hand2")
        but256 = Checkbutton(self.win, text="256*256", variable=self.s256, onvalue=True, offvalue=False, cursor="hand2")
        butdiy = Checkbutton(self.win, text="自定义", variable=self.sdiy, onvalue=True, offvalue=False, cursor="hand2")

        # 图片预览区--------------------------------------------------------------------------------------------------
        image_area = Label(self.win, relief="solid", justify="center")
        self.show_image(get_resource_path("use_ico.png"), image_area)  # 初始化指示图

        # 方法绑定,参数补充-----------------------------------------------------------------------------------------
        selection.config(command=lambda: self.updata_selection_path(self.selection_path, self.output_path, image_area))
        output.config(command=lambda: GO_ICO.updata_output_path(self.output_path))
        butdiy.config(command=lambda: self.updata_text(but24, but32, but48, but64, but96, but128, but192, but256))
        satrt_but.config(command=lambda: self.start_conversion(image_area))

        # 布局------------------------------------------------------------------------------------------------------
        satrt_but.place(relx=0.02, rely=0.02, relwidth=0.19, relheight=0.13)  # 开始转换按钮
        selection.place(relx=0.24, rely=0.02, relwidth=0.19, relheight=0.06)  # 选择文件按钮
        self.selection_path.place(relx=0.43, rely=0.02, relwidth=0.55, relheight=0.06)  # 选择文件路径

        output.place(relx=0.24, rely=0.09, relwidth=0.19, relheight=0.06)  # 选择输出目录按钮
        self.output_path.place(relx=0.43, rely=0.09, relwidth=0.55, relheight=0.06)  # 输出目录路径

        but24.place(relx=0.01, rely=0.16, relwidth=0.14, relheight=0.07)
        but32.place(relx=0.20, rely=0.16, relwidth=0.14, relheight=0.07)
        but48.place(relx=0.39, rely=0.16, relwidth=0.14, relheight=0.07)

        but64.place(relx=0.01, rely=0.21, relwidth=0.14, relheight=0.07)
        but96.place(relx=0.20, rely=0.21, relwidth=0.14, relheight=0.07)
        but128.place(relx=0.39, rely=0.21, relwidth=0.18, relheight=0.07)

        but192.place(relx=0.01, rely=0.26, relwidth=0.18, relheight=0.07)
        but256.place(relx=0.20, rely=0.26, relwidth=0.18, relheight=0.07)
        butdiy.place(relx=0.39, rely=0.26, relwidth=0.18, relheight=0.07)  # 其他尺寸复选框

        image_area.place(relx=0.01, rely=0.33, relwidth=0.98, relheight=0.665)  # 图片预览区

    def updata_selection_path(self, selection_path: Entry, output_path: Entry, show_label):
        """选择本地文件按钮回调，并将选择文件更新到entry"""
        open_path = askopenfilename(title="选择转换文件")
        selection_path.delete(0, END)
        selection_path.insert(0, open_path)
        output_path.delete(0, END)
        output_path.insert(0, dirname(open_path))

        # 展示选择图片
        try:
            self.show_image(open_path, show_label)
        except UnidentifiedImageError:
            messagebox.showwarning(title="解析错误", message="解析失败,请检查地址是否图像地址！!！")
            return

    @staticmethod
    def updata_output_path(output_path: Entry):
        """选择打开文件，并更新打开文件路径和保存路径"""
        save_path = askdirectory(title="选择保存路径")
        output_path.delete(0, END)
        output_path.insert(0, save_path)

    def get_sizes(self):
        """获取尺寸列表"""
        _sizes = list()
        if self.s24.get():
            _sizes.append((24, 24))

        if self.s32.get():
            _sizes.append((32, 32))

        if self.s48.get():
            _sizes.append((48, 48))

        if self.s64.get():
            _sizes.append((64, 64))

        if self.s96.get():
            _sizes.append((96, 96))

        if self.s128.get():
            _sizes.append((128, 128))

        if self.s192.get():
            _sizes.append((192, 192))

        if self.s256.get():
            _sizes.append((256, 256))

        if self.sdiy.get():
            sizes_str: str = self.other_text.get("0.0", END)
            for size in sizes_str.splitlines():  # 按照行切割
                if size == "":  # 空行跳过
                    continue
                img_width_img_height = size.split("*")
                if len(img_width_img_height) != 2:  # 长度不为2错误，跳过
                    raise ValueError

                img_width, img_height = img_width_img_height
                img_width = int(img_width)
                img_height = int(img_height)

                if img_width <= 0 or img_height <= 0:
                    raise ValueError

                _sizes.append((img_width, img_height))
            if len(_sizes) == 0:  # 输入错误的信息
                showerror(title="格式错误", message="未检测到正确的格式,请修改后再试")
                raise ValueError
        return _sizes

    def updata_text(self, *args):
        if self.sdiy.get():
            self.other_text.place(relx=0.57, rely=0.16, relwidth=0.41, relheight=0.17)
            self.s24.set(False)
            self.s32.set(False)
            self.s48.set(False)
            self.s64.set(False)
            self.s96.set(False)
            self.s128.set(False)
            self.s192.set(False)
            self.s256.set(False)
            for i in args:
                i.config(state="disabled")
            self.other_text.delete("0.0", END)
            self.other_text.insert("0.0", "16*16\n24*24\n32*32\n48*48\n64*64\n96*96\n128*128\n192*192\n256*256\n")

        else:
            self.other_text.place_forget()
            for i in args:
                i.config(state="NORMAL")

    def start_conversion(self, show_label):
        """转换按钮回调，执行转换"""
        if self.selection_path.get() == "":
            messagebox.showwarning(title="未选转换文件", message="请先选择转换文件或输入图片url")
            return
        if self.output_path.get() == "":
            self.output_path.insert(0, ".")

        image_path = self.selection_path.get()  # 图像路径
        image_name = GO_ICO.get_filename(image_path)  # 转换后图像名
        save_path = self.output_path.get()  # 转换后保存路径

        try:
            image = self.get_image(image_path)
            self.show_image(image, show_label)  # 执行前先把图像显示出来
        except UnidentifiedImageError:
            messagebox.showwarning(title="解析错误", message="解析失败,请检查地址是否图像地址！！！")
            self.show_image(get_resource_path("use_ico.png"), show_label)
            return

        except (TimeoutError, requests.exceptions.ConnectionError):
            messagebox.showwarning(title="获取指定图像失败", message="无法获取指定图像")
            self.show_image(get_resource_path("use_ico.png"), show_label)
            return

        try:
            sizes = self.get_sizes()
        except ValueError:
            showerror(title="尺寸读取错误", message="自定义尺寸错误！\n以*分割宽高(无需插入任何空格),多尺寸请换行,不要输入无意义内容！！")
            return

        try:
            image.save(f"{os.path.join(save_path, image_name)}.ico", sizes=sizes)
            messagebox.showinfo(title="success", message=f"转换成功,文件保存至：\n{os.path.abspath(self.output_path.get())}")
        except IOError:
            messagebox.showwarning(title="无法写入", message="文件无法写入,请检查文件是否已经创建")
        except ValueError:
            messagebox.showwarning(title="格式错误", message="无法从文件名中确定输出格式！")

    @staticmethod
    def get_image(image_path):
        """根据地址框内容判断输入内容时地址或url,返回"""
        if isfile(image_path):
            image = Image.open(image_path)
            return image

        elif image_path.startswith(("http://", "https://")):
            image_response = requests.get(image_path).content
            return Image.open(BytesIO(image_response))

        elif image_path == "":
            return Image.open(get_resource_path("use_ico.png"))
        else:
            raise UnidentifiedImageError

    @staticmethod
    def get_filename(path: str):
        """把文件名去掉后缀，并加上_ico.ico后缀返回"""
        if isfile(path):  # 本地图有现成的名字
            suffix_name = os.path.basename(path)
            name_list = suffix_name.split(".")
            name_list.pop()
            image_name = f"{''.join(name_list)}_ico"
            return image_name
        else:  # 网络图按照时间给她一个名字
            # 获取当前格式化时间,并处理成%Y_%m_%d_%H_%M_%S.%ms格式时间字符串
            current_timestamp = time.time()
            format_time = time.localtime(current_timestamp)
            file_name = f"{time.strftime('%Y%m%d%H%M%S%m', format_time)}_ico"
            return file_name

    def show_image(self, image, show_label):
        """传入图像地址和显示区域，将图片缩放并显示"""
        if isinstance(image, str):
            pil_image = self.get_image(image)
            resize_image = pil_image.resize(size=GO_ICO.get_resizes_size(pil_image, 392, 330))
            image = ImageTk.PhotoImage(image=resize_image)
        else:
            resize_image = image.resize(size=GO_ICO.get_resizes_size(image, 392, 330))
            image = ImageTk.PhotoImage(image=resize_image)

        show_label.config(image=image)
        show_label.image = image

    @staticmethod
    def get_resizes_size(image: Image.Image, w_width: int, w_height: int):
        """
        image:Image.Image对象
        w_width、w_height最大支持显示宽高
        """
        image_width = image.width
        image_height = image.height
        width_percentage = w_width / image_width
        height_percentage = w_height / image_height
        """
        1、判断图片宽拉满时，高度是否溢出
        2、如果高度小于等显示区域高度，以宽度作为标准
        2、高度大于显示区域高度，以高度作为标准
        """
        if image_height * width_percentage <= w_height:  # 判断图片在显示区域宽拉满时,高度是否溢出
            # 高度不溢出：高度小于等显示区域高度，则以宽度作为标准
            return_width = image_width * width_percentage
            return_height = image_height * width_percentage
        else:
            # 高度溢出：高度大于显示区域高度，以高度作为标准
            return_width = image_width * height_percentage
            return_height = image_height * height_percentage

        return int(return_width), int(return_height)


if __name__ == '__main__':
    # 窗口初始化
    root = Tk()

    # 窗口标题
    root.title("ico生成")

    # 窗口图标
    ico_path = get_resource_path("./p_18.png")
    icon_image = PhotoImage(file=ico_path)
    root.iconphoto(False, icon_image)

    # 桌面长宽
    win_w = root.winfo_screenwidth()
    win_h = root.winfo_screenheight()

    # 窗口长宽
    width = 400
    height = 500

    # 设置窗口位置和长宽
    root.geometry(f"{width}x{height}+{(win_w - width) // 2}+{(win_h - height) // 2}")

    # 不可调整大小
    root.resizable(False, False)

    # 窗口置顶
    root.wm_attributes('-topmost', 1)

    GO_ICO(root)

    # 主循环
    root.mainloop()
